﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Media;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.Diagnostics;
using System.IO;
#endregion using

namespace Turn_ament
{
    #region Tile
    class Tile : ImageObject
    {
        #region Attributes
        protected Point spot; // Spot in 2D tile array
        protected Tile[] neighbors;

        public enum Type { Normal, Blocked /*, LowBlock (you would be able to shoot over, not move over)*/ }
        protected Type type;
        protected float weight; // base value: 1.0f;
        protected Unit occupiedBy;
        #endregion Attributes

        #region Constructor
        public Tile(int x, int y, int size, int xSpot, int ySpot, Type type, float weight)
            : base(null, x, y, size, size, null)
        {
            this.weight = weight;
            this.type = type;
            spot = new Point(xSpot, ySpot);
            neighbors = new Tile[4];
        }
        #endregion Constructor

        #region Propeties
        /// <summary> Where the tile is located in 2D tile Array </summary>
        public Point Spot
        {
            get { return spot; }
        }

        /// <summary> Holds the 4 neighboring Tiles </summary>
        public Tile[] Neighbors
        {
            get { return neighbors; }
        }

        public Type TileType
        {
            get { return type; }

            set { type = value; }
        }

        public float Weight
        {
            get { return weight; }

            set { weight = value; }
        }

        public Unit OccupiedBy
        {
            get { return occupiedBy; }

            set { occupiedBy = value; }
        }
        #endregion Propeties
    }
    #endregion Tile

    #region Map
    class Map
    {
        #region Attributes
        private Tile[,] tiles;
        private Point size;
        #endregion Attributes

        #region Constructor
        public Map(int mapNum)
        {
            int[,] tileMap = LoadMap(mapNum);

            if (tileMap != null)
            {
                tiles = new Tile[tileMap.GetLength(0), tileMap.GetLength(1)];

                int tileSize = Var.TILE_SIZE;
                for (int x = 0; x < tileMap.GetLength(0); x++)
                {
                    for (int y = 0; y < tileMap.GetLength(1); y++)
                    {
                        Tile.Type type = Tile.Type.Normal;
                        float weight = 1;
                        if (tileMap[x, y] == 1)
                            type = Tile.Type.Blocked;
                        if (tileMap[x, y] == 2)
                            weight = 2;
                        tiles[x, y] = new Tile(x * tileSize, y * tileSize, tileSize, x, y, type, weight);
                    }
                }

                size = new Point(tiles.GetLength(0), tiles.GetLength(1));
                AssignTileNeighbors();
            }
        }
        #endregion region Constructor

        #region Properties
        public Tile[,] Tiles
        {
            get { return tiles; }
        }
        #endregion Properties

        #region Methods
        private int[,] LoadMap(int mapNum)
        {
            if (File.Exists("Maps/map" + mapNum + ".map"))
            {
                StreamReader reader = new StreamReader("Maps/map" + mapNum + ".map");

                string message = reader.ReadToEnd();
                message = message.Replace("\r\n", "-");
                string[] rows = message.Split('-');
                string[] rowContents = rows[0].Split(',');
                int[,] mapArray = new int[rowContents.Length, rows.Length];
                for (int y = 0; y < mapArray.GetLength(1); y++)
                {
                    rowContents = rows[y].Split(',');
                    for (int x = 0; x < mapArray.GetLength(0); x++)
                    {
                        mapArray[x, y] = Int32.Parse(rowContents[x]);
                    }
                }
                return mapArray;
            }
            return null;
        }

        public void AssignTileNeighbors()
        {
            // Now for each of the search nodes, we will
            // connect it to each of its neighbours.
            for (int x = 0; x < size.X; x++)
            {
                for (int y = 0; y < size.Y; y++)
                {
                    // An array of all of the possible neighbors this 
                    // node could have. (We will ignore diagonals for now.)
                    Point[] neighbors = new Point[]
                    {
                        new Point (x, y - 1), // The node above the current node
                        new Point (x, y + 1), // The node below the current node.
                        new Point (x - 1, y), // The node left of the current node.
                        new Point (x + 1, y), // The node right of the current node
                    };

                    // We loop through each of the possible neighbors
                    for (int i = 0; i < neighbors.Length; i++)
                    {
                        Point position = neighbors[i];

                        // We need to make sure this neighbour is part of the level.
                        if (position.X < 0 || position.X >= size.X || position.Y < 0 || position.Y >= size.Y)
                        {
                            continue;
                        }

                        // Store a reference to the neighbor.
                        tiles[x, y].Neighbors[i] = tiles[position.X, position.Y];
                    }
                }
            }
        }

        public bool IsTileInRange(Tile startNode, Tile endNode, int range)
        {
            if(Math.Abs(startNode.Spot.X - endNode.Spot.X) + Math.Abs(startNode.Spot.Y - endNode.Spot.Y) > range)
                return false;
            return true;
        }

        public List<Tile> FindPath(Tile startNode, Tile endNode, int range)
        {
            List<Tile> allInRange = new List<Tile>();
            Dictionary<Tile, float> dist = new Dictionary<Tile, float>();
            Dictionary<Tile, Tile> previous = new Dictionary<Tile, Tile>();

            allInRange.Add(startNode);
            dist.Add(startNode, 0);
            previous.Add(startNode, null);
            foreach (Tile tile in tiles)
            {
                if (IsTileInRange(startNode, tile, range) && tile != startNode && tile.TileType == Tile.Type.Normal && tile.OccupiedBy == null)
                {
                    allInRange.Add(tile);
                    dist.Add(tile, Int32.MaxValue);
                    previous.Add(tile, null);
                }
            }
            if (allInRange.Contains(endNode) == false) // moving to current enemy location (via attack)
            {
                allInRange.Add(endNode);
                dist.Add(endNode, Int32.MaxValue);
                previous.Add(endNode, null);
            }

            dist[startNode] = 0;
            Tile curTile = null;
            List<Tile> todo = new List<Tile>();
            todo.Add(startNode);

            while (todo.Count != 0)
            {
                curTile = todo[0];//curTile = GetShortestDist(todo, dist);
                if (dist[curTile] == Int32.MaxValue) // all remaining vertices are inaccessible from source
                    break;
                todo.Remove(curTile);
                if (curTile == endNode)
                    break;
                foreach (Tile neighbor in curTile.Neighbors)
                {
                    if (neighbor != null && allInRange.Contains(neighbor))
                    {
                        float newDist = dist[curTile] + neighbor.Weight; // 1 being the const weight between each tile.
                        if (newDist < dist[neighbor])
                        {
                            dist[neighbor] = newDist;
                            previous[neighbor] = curTile;
                            todo.Add(neighbor);//todo.Remove(neighbor);
                        }
                    }
                }
            }

            List<Tile> returnList = new List<Tile>();
            while (previous[curTile] != null)
            {
                returnList.Add(curTile);
                curTile = previous[curTile];
            }
            returnList.Add(curTile);
            returnList.Reverse();
            return returnList;
        }

        public List<Tile> FindTilesInRange(Tile startNode, int range)
        {
            List<Tile> allInRange = new List<Tile>();
            Dictionary<Tile, int> dist = new Dictionary<Tile, int>();
            Dictionary<Tile, Tile> previous = new Dictionary<Tile, Tile>();

            allInRange.Add(startNode);
            dist.Add(startNode, 0);
            previous.Add(startNode, null);
            foreach (Tile tile in tiles)
            {
                if (IsTileInRange(startNode, tile, range) && tile != startNode && tile.TileType == Tile.Type.Normal && tile.OccupiedBy == null)
                {
                    allInRange.Add(tile);
                    dist.Add(tile, Int32.MaxValue);
                    previous.Add(tile, null);
                }
            }

            dist[startNode] = 0;
            Tile curTile = null;
            List<Tile> todo = new List<Tile>();
            todo.Add(startNode);

            while (todo.Count != 0)
            {
                curTile = todo[0];//curTile = GetShortestDist(todo, dist);
                if (dist[curTile] == Int32.MaxValue) // all remaining vertices are inaccessible from source
                    break;
                todo.Remove(curTile);
                foreach (Tile neighbor in curTile.Neighbors)
                {
                    if (neighbor != null && allInRange.Contains(neighbor))
                    {
                        int newDist = dist[curTile] + 1; // 1 being the const weight between each tile.
                        if (newDist < dist[neighbor])
                        {
                            dist[neighbor] = newDist;
                            previous[neighbor] = curTile;
                            todo.Add(neighbor);//todo.Remove(neighbor);
                        }
                    }
                }
            }

            List<Tile> returnList = new List<Tile>();
            foreach (Tile tile in allInRange)
            {
                int bla = dist[tile];
                if (dist[tile] <= range)
                    returnList.Add(tile);
            }
            return returnList;
        }

        public List<Tile> FindUnitRange(Unit currentUnit, out List<Tile> attackRangeList)
        {
            attackRangeList = new List<Tile>();
            Tile startNode = currentUnit.Tile;
            List<Tile> allInRange = new List<Tile>(); List<Tile> attackRange = new List<Tile>();
            Dictionary<Tile, float> dist = new Dictionary<Tile, float>();
            Dictionary<Tile, Tile> previous = new Dictionary<Tile, Tile>();

            allInRange.Add(startNode);
            dist.Add(startNode, 0);
            previous.Add(startNode, null);
            foreach (Tile tile in tiles)
            {
                if (tile != startNode && tile.TileType == Tile.Type.Normal)
                {
                    if (IsTileInRange(startNode, tile, (int)currentUnit.MovementLeft + currentUnit.Range) && tile.OccupiedBy == null)
                    {
                        allInRange.Add(tile);
                        dist.Add(tile, Int32.MaxValue);
                        previous.Add(tile, null);
                    }
                    else if (IsTileInRange(startNode, tile, (int)currentUnit.MovementLeft + currentUnit.Range) && tile.OccupiedBy != null &&
                        tile.OccupiedBy.Player != currentUnit.Player)
                    {
                        attackRange.Add(tile);
                        dist.Add(tile, Int32.MaxValue);
                        previous.Add(tile, null);
                    }
                }
            }

            dist[startNode] = 0;
            Tile curTile = null;
            List<Tile> todo = new List<Tile>();
            todo.Add(startNode);

            while (todo.Count != 0)
            {
                curTile = todo[0];//curTile = GetShortestDist(todo, dist);
                if (dist[curTile] == Int32.MaxValue) // all remaining vertices are inaccessible from source
                    break;
                todo.Remove(curTile);
                foreach (Tile neighbor in curTile.Neighbors)
                {
                    if (neighbor != null && allInRange.Contains(neighbor))
                    {
                        float newDist = dist[curTile] + neighbor.Weight; // 1 being the const weight between each tile.
                        if (newDist < dist[neighbor])
                        {
                            dist[neighbor] = newDist;
                            previous[neighbor] = curTile;
                            todo.Add(neighbor);//todo.Remove(neighbor);
                        }
                    }
                    else if (neighbor != null && attackRange.Contains(neighbor))
                    {
                        float newDist = dist[curTile] + 1; // 1 being the const weight between each tile.
                        if (newDist < dist[neighbor])
                        {
                            dist[neighbor] = newDist;
                            previous[neighbor] = curTile;
                        }
                    }
                }
            }

            List<Tile> returnList = new List<Tile>();
            foreach (Tile tile in allInRange)
            {
                float bla = dist[tile];
                if (dist[tile] <= currentUnit.MovementLeft)
                    returnList.Add(tile);
                else if (dist[tile] <= currentUnit.MovementLeft + currentUnit.Range)
                    attackRangeList.Add(tile);
            }
            foreach (Tile tile in attackRange)
            {
                if (dist[tile] <= currentUnit.MovementLeft + currentUnit.Range)
                    attackRangeList.Add(tile);
            }
            return returnList;
        }
        #endregion Methods
    }
    #endregion Map
}
